home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 5
/
Aminet 5 - March 1995.iso
/
Aminet
/
mus
/
edit
/
AlgoRhythms.lha
/
AlgoRhythms
/
Source
/
record.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-11-25
|
22KB
|
770 lines
/* Record.c
Copyright (c) 1990,1991,1992,1993 by Thomas E. Janzen
All Rights Reserved
THIS SOFTWARE IS FURNISHED FREE OF CHARGE FOR STUDY AND USE AND MAY
BE COPIED ONLY FOR PERSONAL USE OR COMPLETELY AS OFFERED WITH NO
CHANGES FOR FREE DISTRIBUTION. NO TITLE TO AND OWNERSHIP OF THE
SOFTWARE IS HEREBY TRANSFERRED. THOMAS E. JANZEN ASSUMES NO
RESPONSIBILITY FOR THE USE OR RELIABILITY OF THIS SOFTWARE.
Thomas E. Janzen
208A Olde Derby Road
Norwood, MA 02062-1761
(617)769-7733
** FACILITY:
**
** AlgoRhythms music improviser on Commodore (TM) Amiga (TM)
** compiled with SAS/C Amiga Compiler 6.50
**
** ABSTRACT:
**
** Record.c contains functions for MIDI event recording and saving
**
** AUTHORS: Thomas E. Janzen
**
** CREATION DATE: 9-DEC-1991
**
** MODIFICATION HISTORY:
** DATE NAME DESCRIPTION
** 4 Jan 92 TEJ last changes for 2.0
*/
#include <stdlib.h>
#include <stdio.h>
#include <intuition/intuition.h>
#include <proto/timer.h>
#include <ctype.h>
#include <string.h>
#include "AlgoRhythms.h"
#include "Menus.h"
#include "record.h"
#define NOTES_PER_BLOCK 1024
#define MULTITRAK 1
#define MIDIHDRLEN 14
#define MIDITRKHDRLEN 8
#define TICKS_PER_QUARTER 240
#define NOTEON 0X90
#define META_EVENT 0XFF
#define TEXT_EVENT 0X01
#define TRACK_NAME 0X03
#define EOT_EVENT 0X2F
#define TEMPO_EVENT 0X51
#define TIME_SIG_EVENT 0X58
#define PACK_BYTES(A, B, C, D) (D | (C << 8) | (B << 16) | (A << 24))
struct Note_Record_Struct
{
struct timeval nr_r_event_time;
unsigned char nr_uc_channel,
nr_uc_pitch,
nr_uc_velocity;
};
typedef struct Note_Record_Struct NOTE_RECORD_TYPE;
typedef struct Page_List_Struct PAGE_LIST_TYPE;
struct Page_List_Struct
{
PAGE_LIST_TYPE *pl_r_succ,
*pl_r_pred;
NOTE_RECORD_TYPE *pl_r_page;
};
typedef struct midi_note_struct MIDI_NOTE_TYPE;
struct midi_note_struct
{
unsigned char mn_uc_sts_chanl,
mn_uc_note_num,
nm_velocity;
};
typedef struct MIDI_Header MIDI_HEADER_TYPE;
struct MIDI_Header
{
unsigned int chunk_type,
chunk_len;
unsigned short int format,
num_tracks,
division; /* of time, usu. 24 MIDI ticks */
};
typedef struct MIDI_Track MIDI_TRACK_TYPE;
struct MIDI_Track
{
unsigned int chunk_type,
chunk_len;
};
static unsigned char *midi_score = NULL;
static unsigned int total_notes_qty = 0;
static PAGE_LIST_TYPE page_list_head = {NULL, NULL, NULL};
static PAGE_LIST_TYPE *current_list_ptr = NULL;
unsigned int recording = FALSE;
static int page_note_counter = 0;
static void add_page(void);
static int variable_len_values(int number, unsigned char *array);
static void insert_meta_event(unsigned int *midi_index,
unsigned char *midi_score,
const unsigned char event_type,
const unsigned int event_len);
static void store_a_note( unsigned int *midi_index,
unsigned char *midi_score,
unsigned char current_channel,
unsigned char current_dynamic,
unsigned char pitch,
unsigned char *running_sts);
unsigned int record_init(void)
/*
** FUNCTIONAL DESCRIPTION:
** Sets up the recording data structures
**
** RETURN VALUE:
** description:
** data_type: int
**
** DESIGN:
**
** ROUTINE
** : IF page_list_head.pl_r_page == NULL
** : : page_list_head.pl_r_page = malloc()
** : : IF malloc failed
** : : : return FALSE
** : : ENDIF
** : : initialize list head pred and succ to NULL
** : : current_list_ptr = &page_list_head
** : : total_notes_qty = 0
** : ENDIF
** : return TRUE
** ENDROUTINE
**
*/
{
if (NULL == page_list_head.pl_r_page)
{
page_list_head.pl_r_page
= malloc(sizeof(NOTE_RECORD_TYPE) * NOTES_PER_BLOCK);
if (NULL == page_list_head.pl_r_page)
{
return FALSE; /* recording won't turn on if malloc failed */
}
page_list_head.pl_r_succ = NULL;
page_list_head.pl_r_pred = NULL;
current_list_ptr = &page_list_head;
total_notes_qty = 0;
page_note_counter = 0;
}
return TRUE;
}
int record_note_event(NOTE_EVENT_TYPE *note_event)
/*
** FUNCTIONAL DESCRIPTION:
** Stores a play note event in memory for later writing to a MIDI file
** RETURN VALUE:
** description: TRUE if successful
** data_type: int
**
** ARGUMENTS:
**
** note_event-
** description: a single voice
** data_type: pointer to NOTE_EVENT_TYPE
** access: read/write only
**
** DESIGN:
**
** ROUTINE record_note_event
** : IF current_list_ptr->pl_r_page == NULL
** : : current_list_ptr->pl_r_page = malloc()
** : : IF malloc failed
** : : : turn off recording
** : : : clear_record()
** : : : return FALSE
** : : ENDIF
** : ENDIF
** : IF this block of notes is full
** : : add_page()
** : : clear note_event_index
** : ENDIF
** : copy pitch, channel and dynamic from note_event to note_record
** : IF note is playing
** : : copy note_event start_time to note_record event_time
** : ELSE the note is being turned off
** : : copy note_event stop_time to note_record event_time
** : ENDIF
** : append note_record into the data array pointed to by linked list
** : increment note_event_index
** : increment notes_qty
** : return TRUE
** ENDROUTINE
**
*/
{
auto NOTE_RECORD_TYPE note_record;
if (NULL == current_list_ptr->pl_r_page)
{
current_list_ptr->pl_r_page
= malloc(sizeof(NOTE_RECORD_TYPE) * NOTES_PER_BLOCK);
if (NULL == current_list_ptr->pl_r_page)
{
recording = FALSE;
clear_record();
/* post an error message */
return FALSE;
}
}
if (page_note_counter >= NOTES_PER_BLOCK)
{
add_page();
}
note_record.nr_uc_pitch = note_event->nv_i_cur_pitch;
note_record.nr_uc_channel = note_event->nv_i_channel;
note_record.nr_uc_velocity = note_event->nv_i_dynamic;
if (note_event->nv_i_playing) /* it's a note on */
{
note_record.nr_r_event_time = note_event->nv_r_start_time;
}
else
{
note_record.nr_r_event_time = note_event->nv_r_stop_time;
}
memcpy(&(current_list_ptr->pl_r_page[page_note_counter]),
¬e_record, sizeof(NOTE_RECORD_TYPE));
page_note_counter++;
total_notes_qty++;
return TRUE;
}
static void add_page(void)
/*
** FUNCTIONAL DESCRIPTION:
** appends another page for MIDI data to linked list
**
** DESIGN:
**
** ROUTINE add_page
** : current_list_ptr->pl_r_succ = malloc()
** : IF malloc failed
** : : stop recording
** : : clear_record()
** : : return
** : ENDIF
** : current_list_ptr->pl_r_succ->pl_r_pred = current_list_ptr
** : current_list_ptr = current_list_ptr->pl_r_succ
** : current_list_ptr->pl_r_page = malloc(page)
** : IF malloc failed
** : : stop recording
** : : clear_record()
** : : return
** : ENDIF
** : current_list_ptr->pl_r_succ = NULL
** : return
** ENDROUTINE
**
*/
{
current_list_ptr->pl_r_succ = malloc(sizeof(PAGE_LIST_TYPE));
if (NULL == current_list_ptr->pl_r_succ)
{
recording = FALSE;
clear_record();
return;
}
current_list_ptr->pl_r_succ->pl_r_pred = current_list_ptr;
current_list_ptr = current_list_ptr->pl_r_succ;
current_list_ptr->pl_r_page
= malloc(sizeof(NOTE_RECORD_TYPE) * NOTES_PER_BLOCK);
if (NULL == current_list_ptr->pl_r_page)
{
recording = FALSE;
clear_record();
/* post an error message */
return;
}
current_list_ptr->pl_r_succ = NULL;
page_note_counter = 0;
return;
}
void erase_recording(void)
/*
** FUNCTIONAL DESCRIPTION:
** Dissassembles the linked list of recorded MIDI data.
**
** DESIGN:
**
** ROUTINE erase_recording
** : tmp_entry = &page_list_head
** : WHILE not at end of page list
** : : tmp_entry = tmp_entry->pl_r_succ
** : ENDWHILE
** : WHILE not at beginning of list
** : : free page of this entry
** : : step back one list entry
** : : free successor of this list entry
** : ENDWHILE
** : zero out total_notes_qty
** ENDROUTINE
**
*/
{
auto PAGE_LIST_TYPE *tmp_entry;
tmp_entry = &page_list_head;
while (tmp_entry->pl_r_succ != NULL)
{
tmp_entry = tmp_entry->pl_r_succ;
}
/*
** we are now pointing at the last list entry
** so unwind the data
*/
while (tmp_entry->pl_r_pred != NULL)
{
free(tmp_entry->pl_r_page);
tmp_entry->pl_r_page = NULL;
tmp_entry = tmp_entry->pl_r_pred;
free(tmp_entry->pl_r_succ);
tmp_entry->pl_r_succ = NULL;
}
total_notes_qty = 0;
/*
** We should now point to the head of the list
*/
return;
}
int write_midi(char *midi_file_nam)
/*
** FUNCTIONAL DESCRIPTION:
** Write the recording buffer to a MIDI file
**
** RETURN VALUE:
** description: TRUE if successful
** data_type: int
**
** ARGUMENTS:
**
** midi_file_nam-
** description: name of MIDI file to which to write the record buff
** data_type: pointer to char
** access: read/write only
**
** DESIGN:
**
** ROUTINE write_midi
** : local_list_ptr = &page_list_head
** : midi_index = 0
** : midi_score = malloc(room for all of the notes and then some)
** : IF malloc failed
** : : return FALSE
** : ENDIF
** : midi_score[midi_index] = 0
** : midi_index++
** : insert track name meta event into midi_score
** : append 0 delay byte to midi_score
** : append tempo meta event to midi_score
** : append 0 delay byte to midi_score
** : append 4/4 meter meta event to midi_score
** : FOR all the recorded notes
** : : IF ran out of notes in this block
** : : : go to next block
** : : : IF this list member is NULL OR its page pointer is NULL
** : : : : free midi_score
** : : : : return FALSE
** : : : ENDIF
** : : : intra_block_index = 0
** : : ENDIF
** : : copy note_record from the linked list page
** : : increment intra_block_index
** : : copy pitch, channel and velocity from note_record to note_event
** : : tmp_time = note_record event_time
** : : IF local_total_notes_qty == 0
** : : : last_time = tmp_time
** : : ENDIF
** : : IF tmp_time > last_time
** : : : fix last_time and time_int
** : : ELSE
** : : : tmp_time = tmp_time - last_time
** : : : last_time = note_record event_time
** : : : convert tmp_time into MIDI delay
** : : ENDIF
** : : store the note in midi_score
** : ENDFOR
** : add a trailing delay
** : increment midi_index
** : add end of track MIDI meta event in midi_score
** : open midi file
** : IF file open failed
** : : free midi_score
** : : return FALSE
** : ENDIF
** : write MIDI header to the midi file
** : write track type chunk to file
** : write midi_score to file
** : flush and close file
** : free midi_score
** : return TRUE
** ENDROUTINE
**
*/
{
auto unsigned char filemode[2] = "w",
running_sts = 0,
track_nam[32] = "AlgoRhythms by Tom Janzen";
auto unsigned int midi_index = 0,
local_total_notes_qty = 0,
intra_block_index = 0,
time_int,
time_len;
auto struct timeval tmp_time = {0, 0},
last_time = {0, 0};
auto FILE *midi_file = NULL;
auto NOTE_RECORD_TYPE note_record;
auto MIDI_NOTE_TYPE note_event;
auto PAGE_LIST_TYPE *local_list_ptr = NULL;
auto MIDI_HEADER_TYPE midi_file_hdr =
{PACK_BYTES ('M','T','h','d'), 6,
MULTITRAK, 1, TICKS_PER_QUARTER};
auto MIDI_TRACK_TYPE track = {PACK_BYTES ('M', 'T', 'r', 'k'), 0};
local_list_ptr = &page_list_head; /* initialize list pointer */
midi_index = 0;
/*
** can't be larger than * 8 + header;
** note = 4 bytes delay max, 3 for event max
*/
midi_score = malloc((total_notes_qty * 10) + 18 + 4);
if (NULL == midi_score)
{
return FALSE;
}
midi_score[midi_index] = 0;
midi_index++;
insert_meta_event(&midi_index, midi_score, TRACK_NAME,
strlen(track_nam));
strcpy(&midi_score[midi_index], track_nam);
midi_index += strlen(track_nam);
midi_score[midi_index] = 0;
midi_index++;
insert_meta_event(&midi_index, midi_score, TEMPO_EVENT, 3);
midi_score[midi_index] = 0x0F;
midi_index++;
midi_score[midi_index] = 0x42;
midi_index++;
midi_score[midi_index] = 0x40; /* these 3 numbers give
** 1 million ticks/quarter note */
midi_index++;
midi_score[midi_index] = 0; /* required zero delay */
midi_index++;
insert_meta_event(&midi_index, midi_score, TIME_SIG_EVENT, 4);
midi_score[midi_index] = 4;
midi_index++;
midi_score[midi_index] = 2;
midi_index++;
midi_score[midi_index] = 24;
midi_index++;
midi_score[midi_index] = 8;
midi_index++;
for ( local_total_notes_qty = 0;
local_total_notes_qty < total_notes_qty;
local_total_notes_qty++)
{
if (intra_block_index >= NOTES_PER_BLOCK)
{
local_list_ptr = local_list_ptr->pl_r_succ;
if ((NULL == local_list_ptr)
|| (NULL == local_list_ptr->pl_r_page))
{
free(midi_score);
midi_score = NULL;
return FALSE; /* post error */
}
intra_block_index = 0;
}
memcpy(¬e_record,
&(local_list_ptr->pl_r_page[intra_block_index]),
sizeof(NOTE_RECORD_TYPE));
intra_block_index++;
note_event.mn_uc_note_num = note_record.nr_uc_pitch;
note_event.mn_uc_sts_chanl = NOTEON | note_record.nr_uc_channel;
note_event.nm_velocity = note_record.nr_uc_velocity;
/*
** convert absolute time struct to MIDI delay bytes
*/
tmp_time = note_record.nr_r_event_time;
if (0 == local_total_notes_qty)
{
last_time = tmp_time;
}
if (1 == CmpTime(&tmp_time, &last_time)) /* -1, first > second */
/*
** The new time can be earlier than the last time if the user
** stopped the music and hit "Play" i.e., from the beginning
*/
{
last_time = tmp_time;
time_int = 0;
}
else
{
SubTime(&tmp_time, &last_time); /* f(a, b), a = a - b */
last_time = note_record.nr_r_event_time;
time_int
= (tmp_time.tv_micro / 1000) + (tmp_time.tv_secs * 1000);
time_int = (time_int * 240) / 1000; /* give 240/second */
}
time_len
= variable_len_values(time_int, &midi_score[midi_index]);
midi_index += time_len;
store_a_note(&midi_index, midi_score, note_event.mn_uc_sts_chanl,
note_event.nm_velocity, note_event.mn_uc_note_num,
&running_sts);
}
midi_score[midi_index] = 0;
midi_index++;
insert_meta_event(&midi_index, midi_score, EOT_EVENT, 0);
midi_file = fopen(midi_file_nam, filemode);
if (NULL == midi_file)
{
free(midi_score);
midi_score = NULL;
/* report an error */
return FALSE;
}
fwrite((char *)&midi_file_hdr,sizeof(MIDI_HEADER_TYPE), 1, midi_file);
track.chunk_len = midi_index;
fwrite((char *)&track, sizeof(MIDI_TRACK_TYPE), 1, midi_file);
fwrite((char *)midi_score, midi_index, 1, midi_file);
fflush(midi_file);
fclose(midi_file);
free(midi_score);
midi_score = NULL;
return TRUE;
}
static int variable_len_values(int number, unsigned char *array)
/*
** FUNCTIONAL DESCRIPTION:
** Converts an integer to a variable-length MIDI multi-byte value
**
** RETURN VALUE:
** description: number of bytes in variable-length MIDI value
** data_type: int
**
** ARGUMENTS:
**
** number-
** description: number to convert to multi-byte MIDI file value
** data_type: int
** access: read only
**
** array-
** description: The array into which to store the multi-byte value
** data_type: pointer to char
** access: write only
**
** DESIGN:
**
** ROUTINE variable_len_values
** : ls_byte = number & x7F
** : mid_byte = number & x3F80 >> 7 | x80
** : ms_byte = number & x1FC000 >> 14 | x80
** : byte_4 = number & xFE00000 >> 21 | x80
** : IF byte_4 had a number in it
** : : array[0] = byte_4
** : : array[1] = ms_byte
** : : array[2] = mid_byte
** : : array[3] = ls_byte
** : : return 4
** : ENDIF
** : IF ms_byte had a value in it
** : : array[0] = mid_byte
** : : array[1] = ms_byte
** : : array[2] = ls_byte
** : : return 3
** : ENDIF
** : IF mid_byte had a value in it
** : : array[0] = mid_byte
** : : array[1] = ls_byte
** : : return 2
** : ENDIF
** : array[0] = ls_byte
** : return 1
** ENDROUTINE
*/
{
auto unsigned char byte_4,
ms_byte,
mid_byte,
ls_byte;
ls_byte = (number & 0x0000007F);
mid_byte = ((number & 0x00003F80) >> 7) | 0x80;
ms_byte = ((number & 0x001FC000) >> 14) | 0x80;
byte_4 = ((number & 0x0FE00000) >> 21) | 0x80;
if (byte_4 != 0x80)
{
array[0] = byte_4;
array[1] = ms_byte;
array[2] = mid_byte;
array[3] = ls_byte;
return 4;
}
if (ms_byte != 0x80)
{
array[0] = ms_byte;
array[1] = mid_byte;
array[2] = ls_byte;
return 3;
}
if (mid_byte != 0x80)
{
array[0] = mid_byte;
array[1] = ls_byte;
return 2;
}
array[0] = ls_byte;
return 1;
}
static void insert_meta_event(unsigned int *midi_index,
unsigned char *midi_score,
const unsigned char event_type,
const unsigned int event_len)
/*
** FUNCTIONAL DESCRIPTION:
** Adds a meta event to a MIDI score
**
** ARGUMENTS:
**
** midi_index-
** description: position in midi_score area
** data_type: pointer to unsigned int
** access: read/write
**
** midi_score-
** description: midi score area
** data_type: pointer to unsigned char
** access: write only
**
** event_type-
** description: type of meta event being inserted
** data_type: char
** access: read only
**
** event_len-
** description: length of meta event
** data_type: unsigned int
** access: read only
**
** DESIGN:
**
** ROUTINE insert_meta_event
** : midi_score[*midi_index] = META_EVENT marker
** : increment midi_index
** : midi_score[*midi_index] = event_type
** : increment midi_index
** : midi_score[*midi_index] = event_len
** : increment midi_index
** ENDROUTINE
*/
{
midi_score[*midi_index] = META_EVENT;
(*midi_index)++;
midi_score[*midi_index] = event_type;
(*midi_index)++;
midi_score[*midi_index] = event_len;
(*midi_index)++;
return;
}
static void store_a_note( unsigned int *midi_index,
unsigned char *midi_score,
unsigned char current_channel,
unsigned char current_dynamic,
unsigned char pitch,
unsigned char *running_sts)
/*
** FUNCTIONAL DESCRIPTION:
** Put a note event into midi_score
**
** ARGUMENTS:
**
** midi_index-
** description: position in internal MIDI score array
** data_type: pointer to int
** access: read/write
**
** midi_score-
** description: internal MIDI score array for recording
** data_type: pointer to char
** access: write only
**
** current_channel-
** description: MIDI channel
** data_type: unsigned char
** access: read only
**
** current_dynamic-
** description: MIDI dynamic
** data_type: unsigned char
** access: read only
**
** pitch-
** description: MIDI pitch
** data_type: unsigned char
** access: read only
**
** running_sts-
** description: current running status
** data_type: pointer to unsigned char
** access: read/write
**
** DESIGN:
**
** ROUTINE store_a_note
** : copy channel, pitch, dynamic to midi_note
** : IF chanl == file running status
** : : copy only pitch and dynamic into midi_score
** : ELSE
** : : copy channel/noteon, pitch and dynamic into midi_score
** : : set new running status for file
** : ENDIF
** ENDROUTINE
*/
{
static MIDI_NOTE_TYPE midi_note;
midi_note.mn_uc_sts_chanl = current_channel;
midi_note.mn_uc_note_num = pitch;
midi_note.nm_velocity = current_dynamic;
if (midi_note.mn_uc_sts_chanl == *running_sts)
{
memcpy( &midi_score[*midi_index], &(midi_note.mn_uc_note_num), 2);
(*midi_index) += 2;
}
else /* running status changed */
{
memcpy(&midi_score[*midi_index], &midi_note, sizeof midi_note);
(*midi_index) += sizeof(MIDI_NOTE_TYPE);
*running_sts = midi_note.mn_uc_sts_chanl;
}
return;
}